/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		acluialt editsecurity.cpp
 * PURPOSE:		replacement for EditSecurity() and CreateSecurityPage() of system standard aclui.dll
 */

namespace Acluialt
{

struct EditSecurityObj
{
	auto_refct<GuiLibResources> Glr;
	CommandInstance Ci;
	CComPtr<ISecurityInformation> SecInfo;
	auto_array<auto_szpod<WCHAR> > PathComponents;
	SI_OBJECT_INFO ObjInfo;
	PCWSTR SpecificHeaders[DESC_SPECIFIC_SIZE];
	auto_szpod<WCHAR> SpecificHeadersBacking[DESC_SPECIFIC_SIZE];
	ModuleResourceAccumulator ModRes;
	HWND EditorDlg;
	ULONG ExtraSDFlags;

	EditSecurityObj(GuiLibResources *Glr, ISecurityInformation *SecInfo, SI_OBJECT_INFO const &ObjInfo, PCWSTR *SpecificHeaders, auto_szpod<WCHAR> *SpecificHeadersBacking, ULONG ExtraSDFlags) :
		Glr(Glr),
		Ci(NULL),
		SecInfo(SecInfo),
		ObjInfo(ObjInfo),
		EditorDlg(NULL),
		ExtraSDFlags(ExtraSDFlags)
	{
		for(unsigned i = 0; i < DESC_SPECIFIC_SIZE; i++)
		{
			this->SpecificHeaders[i] = SpecificHeaders[i];
			this->SpecificHeadersBacking[i] = SpecificHeadersBacking[i].release();
		}

		Ci.LoadExtraKeyOptions(L"SOFTWARE\\SDEdit\\acluialt");

		if(ObjInfo.pszServerName && ObjInfo.pszServerName[0])
		{
			Ci.GlobalOpts.Opts[GoiHost].Flags |= SDE_OPT_SPECIFIED | SDE_OPT_SET;
			Ci.GlobalOpts.Opts[GoiHost].String = ObjInfo.pszServerName;
		}

		Ci.FinishInit();

		Ci.AddAllModuleResources(ModRes, 0);
	}

	void SetInhSource(IN ACL *Acl, IN OUT ACL_AND_STATE &SdeAcl, ISecurityObjectTypeInfo *SecInfoInh, IN SECURITY_INFORMATION ReadSi)
	{
		OpText Ot(L"Getting source of inherited ACEs");
		
		CppCancelOneHandlerExistance CCOHE;
		try
		{
			ptr_buffer<INHERITED_FROM> Sources;

			RaiseOnComError(SecInfoInh->GetInheritSource(ReadSi, Acl, &Sources.data));

			SdeAcl.State |= ACL_FLAG_AUTO_INH_SOURCES;

			ptr_buffer<ACE> Aces((ACE *)SdeAcl.Acl.get());
			Aces.pos += sizeof(ACL);

			for(USHORT i = 0; i < SdeAcl.Acl->AceCount; i++)
			{
				if(Sources.data->GenerationGap == -1) //source unknown, default state for an ACE
					goto next;

				if(Sources.data->GenerationGap == 0) //direct ACE
					goto next;

				Aces.data->Flags |= ACE_FLAG_INH_SOURCE_KNOWN;
				Aces.data->Source = narrowing_cast<USHORT>(Sources.data->GenerationGap);

				PathComponents.increase_ct(Sources.data->GenerationGap);

				if(!this->PathComponents[Sources.data->GenerationGap - 1]) //if the name needs to be filled in, -1 because the object's own name is not included
					this->PathComponents[Sources.data->GenerationGap - 1] = Sources.data->AncestorName;

			next:
				Aces.pos += Aces.data->Size;
				Sources.pos += sizeof(INHERITED_FROM);
			}

			Ot;
		}
		catch(CancelOneException)
		{}
	}

	//query specified
	void QueryEditState(IN SECURITY_INFORMATION ReadSi, OUT SD_EDIT_STATE &Es, BOOLEAN QueryDefault = FALSE)
	{
		OpText Ot(L"Getting edit state from application", ObjInfo.pszObjectName);
		ModifyPrivs Mp(Ci.GlobalOpts.Opts[GoiPrivPack].SubList);

		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE, Win32_LocalHeap_pod_allocator> Sd;

		while(TRUE)
		{
			HRESULT Result = SecInfo->GetSecurity(ReadSi, reinterpret_cast<PSECURITY_DESCRIPTOR *>(Sd.assign_indirect()), QueryDefault);

			if(SUCCEEDED(Result))
				break;

			if(ReadSi & SACL_SECURITY_INFORMATION &&
			  (Result == E_ACCESSDENIED ||
			  Result == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_ACCESS_DENIED) ||
			  Result == MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_PRIVILEGE_NOT_HELD)))
				ReadSi &= ~(SACL_SECURITY_INFORMATION);
			else
				RaiseOnComError(Result);
		}

		SECURITY_INFORMATION WriteSi = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION;

		if(ObjInfo.dwFlags & SI_OWNER_READONLY)
			WriteSi &= ~OWNER_SECURITY_INFORMATION;

		if(ObjInfo.dwFlags & SI_READONLY)
			WriteSi = 0;

		SDConv::NtSdToEs(Sd, ReadSi, WriteSi, Es, 0);

		ISecurityObjectTypeInfo *SecInfoInh = NULL;

		if(SUCCEEDED(SecInfo->QueryInterface(IID_ISecurityObjectTypeInfo, (PVOID*)&SecInfoInh))) //if ISecurityObjectTypeInfo is supported
		{
			if(Es.Dacl.Acl)
				SetInhSource((ACL *)((ULONG_PTR)Sd.get() + Sd->Dacl), Es.Dacl, SecInfoInh, DACL_SECURITY_INFORMATION);
			if(Es.Sacl.Acl)
				SetInhSource((ACL *)((ULONG_PTR)Sd.get() + Sd->Sacl), Es.Sacl, SecInfoInh, SACL_SECURITY_INFORMATION);
		}

		if(ObjInfo.dwFlags & SI_OBJECT_GUID)
			Es.State |= SD_FLAG_OBJECT_SUPPORT;

		if(!(Es.State & SD_FLAG_OBJECT_SUPPORT)) //let's see if it contains any object ACEs
			if(DoesAclContainAceMatcingPredicate(Es.Dacl.Acl, &IsObjectAce) ||
			   DoesAclContainAceMatcingPredicate(Es.Sacl.Acl, &IsObjectAce))
				Es.State |= SD_FLAG_OBJECT_SUPPORT;

		//pass along possibilty of a default value
		if(ObjInfo.dwFlags & (SI_RESET | SI_RESET_SACL))
			Es.Sacl.State |= ACL_FLAG_DEFAULT_AVAILABLE;

		if(ObjInfo.dwFlags & (SI_RESET | SI_RESET_DACL))
			Es.Dacl.State |= ACL_FLAG_DEFAULT_AVAILABLE;

		if(ObjInfo.dwFlags & (SI_RESET | SI_RESET_OWNER))
			Es.Owner.State |= SID_FLAG_DEFAULT_AVAILABLE;

		if(ObjInfo.dwFlags & (SI_RESET))
			Es.Group.State |= SID_FLAG_DEFAULT_AVAILABLE;

		//pass along merge control ability
		if(ObjInfo.dwFlags & SI_OWNER_RECURSE)
		{
			Es.Owner.State |= SID_FLAG_MERGE_CANON_CTRL;
			Es.Group.State |= SID_FLAG_MERGE_CANON_CTRL;
		}

		if(ObjInfo.dwFlags & SI_RESET_DACL_TREE)
			Es.Dacl.State |= ACL_FLAG_MERGE_CANON_CTRL;

		if(ObjInfo.dwFlags & SI_RESET_SACL_TREE)
			Es.Sacl.State |= ACL_FLAG_MERGE_CANON_CTRL;

		//if(!(ObjInfo.dwFlags & SI_NO_TREE_APPLY) && (ObjInfo.dwFlags | SI_NO_ACL_PROTECT))
		//{
		//	Es.Owner.State |= SID_FLAG_MERGE_CANON_CTRL;
		//	Es.Group.State |= SID_FLAG_MERGE_CANON_CTRL;
		//	Es.Sacl.State |= ACL_FLAG_MERGE_CANON_CTRL;
		//	Es.Dacl.State |= ACL_FLAG_MERGE_CANON_CTRL;
		//}

		//pass along misc
		if(ObjInfo.dwFlags & SI_CONTAINER)
			Es.State |= SD_FLAG_CONTAINER;

		if(Mp.Privileges & PRIV_RELABEL)
			Es.State |= SD_FLAG_LABEL_SET_ANY;

		Es.State |= ExtraSDFlags;

		Ot;
	}

	//query max
	void QueryEditState(OUT SD_EDIT_STATE &Es, BOOLEAN QueryDefault = FALSE)
	{
		QueryEditState(OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, Es, QueryDefault);
	}

	void RaiseInconsistentMergeError(PCWSTR Object)
	{
		RaiseTextError(L"Canonical ACL merging requires you either 1. both merge with inherited entries only and override protected ACLs 2. merge inherited entries after direct entries.", NULL, Object, L"Canonical SD merge mode required.");
	}

	void SetEditState(IN SD_EDIT_STATE const &Es)
	{
		OpText Ot(L"Returning edit state to application", ObjInfo.pszObjectName);

		ModifyPrivs Mp(Ci.GlobalOpts.Opts[GoiPrivPack].SubList);

		SECURITY_INFORMATION Si;
		auto_szpod<SECURITY_DESCRIPTOR_RELATIVE> Sd = SDConv::EsToNtSd(Es, Si);

		if((Es.Owner.State & SID_MERGE_MODE_BITS) == SID_MERGE_MODE_REPLACE)
			Si |= SI_OWNER_RECURSE;

		//pass along replace-all with inherited option
		{
			BOOL M = (Es.Dacl.State & ACL_MERGE_MODE_BITS) == ACL_MERGE_MODE_INH_ONLY; //as opposed to ACL_MERGE_MODE_INH_LAST, the only other valid option in canon mode
			BOOL U = (Es.Dacl.State & ACL_FLAG_MERGE_UNPROTECT) != 0;

			if(M != U)
				RaiseInconsistentMergeError(L"DACL"); 
			else if(M && U)
				Si |= SI_RESET_DACL_TREE;
			//else both false
		}

		{
			BOOL M = (Es.Sacl.State & ACL_MERGE_MODE_BITS) == ACL_MERGE_MODE_INH_ONLY; //as opposed to ACL_MERGE_MODE_INH_LAST, the only other valid option in canon mode
			BOOL U = (Es.Sacl.State & ACL_FLAG_MERGE_UNPROTECT) != 0;

			if(M != U)
				RaiseInconsistentMergeError(L"SACL"); 
			else if(M && U)
				Si |= SI_RESET_SACL_TREE;
			//else both false
		}

		RaiseOnComError(SecInfo->SetSecurity(Si, Sd));

		Ot;
	}
};

EditSecurityObj *CommonEditSecurity(ISecurityInformation *SecInfo)
{
	auto_refct<GuiLibResources> Glr = ReferenceGlr();

	OpText Ot(L"Getting object information from application");

	SI_OBJECT_INFO ObjInfo;
	RtlZeroStruct(ObjInfo);

	RaiseOnComError(SecInfo->GetObjectInformation(&ObjInfo));

	SI_ACCESS *SiRights;
	ULONG SiRightCount;
	ULONG SiDefaultRight;
	ULONG ExtraSDFlags = 0;

	RaiseOnComError(SecInfo->GetAccessRights(NULL, SI_ADVANCED, &SiRights, &SiRightCount, &SiDefaultRight));

	PCWSTR SpecificHeaders[DESC_SPECIFIC_SIZE];
	auto_szpod<WCHAR> SpecificRightsBacking[DESC_SPECIFIC_SIZE]; //contents to be transferred to EditSecurityObj, copies of strings in resources

	RtlZeroMemory(SpecificHeaders, sizeof SpecificHeaders);

	//find specific right labels in SI_ACCESS array
	for(unsigned i = 0; i < DESC_SPECIFIC_SIZE; i++)
	{
		for(unsigned j = 0; j <= SiRightCount; j++)
		{
			ACCESS_MASK SearchBit = 1 << i;

			if(SiRights[j].mask & SearchBit && !(SiRights[j].mask & ~SearchBit)) //if it names the bit we're looking for (and no others)
			{
				if(IS_INTRESOURCE(SiRights[j].pszName))
				{
					SpecificRightsBacking[i].realloc_ct(MAX_STRING_CHARS);

					LoadString(ObjInfo.hInstance, (UINT)(ULONG_PTR)(SiRights[j].pszName), SpecificRightsBacking[i], MAX_STRING_CHARS);

					SpecificHeaders[i] = SpecificRightsBacking[i];
				}
				else
					SpecificHeaders[i] = SiRights[j].pszName;

				break; //break to continue outer loop
			}
		}
	}

	GUID NullGuid;
	RtlZeroStruct(NullGuid);

	//let's see if any GUID specific rights are available, to see if GUID support is suggested
	for(unsigned i = 0; i <= SiRightCount; i++)
		if(RtlEqualMemory(&SiRights[i].pguid, &NullGuid, sizeof GUID))
			ExtraSDFlags |= SD_FLAG_OBJECT_SUPPORT;

	EditSecurityObj *Eso = new EditSecurityObj(Glr, SecInfo, ObjInfo, SpecificHeaders, SpecificRightsBacking, ExtraSDFlags);

	return Eso;
}

void DeleteEditSecurityObj(EditSecurityObj *Eso)
{
	delete Eso;
}

static ULONG_PTR CALLBACK RecieveNotify(PVOID Context, DIALOG_NOTIFY_OPERATION Operation, ULONG_PTR Data)
{
	EditSecurityObj *Context2 = reinterpret_cast<EditSecurityObj *>(Context);

	switch(Operation)
	{
	case DN_APPLY:
	case DN_CLOSE_OK:
		{
			SD_EDIT_STATE &NewState = *reinterpret_cast<SD_EDIT_STATE *>(Data);

			Context2->SetEditState(NewState);
		}
		break;

	case DN_QUERY_DEFAULT:
		{
			DN_QUERY_DEFAULT_ES *Default = c_cast<DN_QUERY_DEFAULT_ES *>(Data);

			Context2->QueryEditState(Default->Es, TRUE);
		}
		break;
	}

	if(Operation <= DN_CLOSE_MAX)
	{
		Context2->EditorDlg = NULL;
		PostQuitMessage(0);
	}

	return 0;
}

void OpenEditSecurityObj(EditSecurityObj *Eso, HWND Parent)
{
	if(Eso->EditorDlg)
		SetForegroundWindow(Eso->EditorDlg);
	else
	{
		SD_EDIT_STATE Es;

		Eso->QueryEditState(Es);

		auto_refct<SdEditor::WindowObject> wob = new SdEditor::WindowObject(*Eso->Ci.GuiPrefs, Eso->SpecificHeaders, Es, Eso->ObjInfo.pszObjectName, &Eso->ModRes.Translators, &Eso->ModRes.Enumerators, Eso->PathComponents, Parent, Eso, &RecieveNotify);

		Eso->EditorDlg = wob->Wnd;

		MessagePump();
	}
}

BOOL WINAPI SdeEditSecurityGuarded(HWND Owner, ISecurityInformation *SecInfo) //exported as EditSecurity
{
	auto_free<EditSecurityObj> Eso = auto_take_(CommonEditSecurity(SecInfo));

	OpenEditSecurityObj(Eso, Owner);

	return TRUE;
}

BOOL WINAPI SdeEditSecurity(HWND Owner, ISecurityInformation *SecInfo) //exported as EditSecurity
{
	__try
	{
		return SdeEditSecurityGuarded(Owner, SecInfo);
	}
	__except(ShowErrorMessageBox(exception_info(), Owner))
	{
		return FALSE;
	}
}

HPROPSHEETPAGE WINAPI SdeCreateSecurityPage(ISecurityInformation *SecInfo)
{
	__try
	{
		EditSecurityObj *Eso = CommonEditSecurity(SecInfo);

		PROPSHEETPAGE Page;

		InitPage(&Page, Eso);

		return CreatePropertySheetPage(&Page);
	}
	__except(ShowErrorMessageBox(exception_info(), NULL))
	{}

	return NULL;
}

}//namespace Acluialt